/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "StdAfx.h"
#include "usfsubtitles.h"
#include <msxml.h>

#define DeclareNameAndValue(pNode, name, val) \
    CComBSTR name; \
    pNode->get_nodeName(&name); \
	name.ToLower(); \
	CComVariant val; \
    pNode->get_nodeValue(&val); \
 
#define BeginEnumAttribs(pNode, pChild, name, value) \
	{CComPtr<IXMLDOMNamedNodeMap> pAttribs; \
	if(SUCCEEDED(pNode->get_attributes(&pAttribs)) && pAttribs != NULL) \
    { \
		CComPtr<IXMLDOMNode> pChild; \
        for(pAttribs->nextNode(&pChild); pChild; pChild = NULL, pAttribs->nextNode(&pChild)) \
        { \
 
#define EndEnumAttribs }}}

#define BeginEnumChildren(pNode, pChild) \
	{CComPtr<IXMLDOMNode> pChild, pNext; \
	for(pNode->get_firstChild(&pChild); pChild; pNext = NULL, pChild->get_nextSibling(&pNext), pChild = pNext) \
    { \
 
#define EndEnumChildren }}

static CStringW GetText(CComPtr<IXMLDOMNode> pNode)
{
    CComBSTR bstr;
    pNode->get_text(&bstr);

    return(CStringW(bstr));
}

static CStringW GetXML(CComPtr<IXMLDOMNode> pNode)
{
    CComBSTR bstr;
    pNode->get_xml(&bstr);
    CStringW str(bstr);
    str.Remove('\r');
    str.Replace('\n', ' ');
    for(size_t i = 0; (i = str.Find(L" ", i)) >= 0;)
    {
        for(++i; i < str.GetLength() && (str[i] == '\n' || str[i] == ' ');)
            str.Delete(i);
    }
    return(str);
}

static CStringW GetAttrib(CStringW attrib, CComPtr<IXMLDOMNode> pNode)
{
    CStringW ret;

    BeginEnumAttribs(pNode, pChild, name, val)
    {
        DeclareNameAndValue(pChild, name, val);

        if(CStringW(name) == attrib && val.vt == VT_BSTR) // TODO: prepare for other types
        {
            ret = val.bstrVal;
            break;
        }
    }
    EndEnumAttribs

    return(ret);
}

static int TimeToInt(CStringW str)
{
    CAtlList<CStringW> sl;
    int i = 0;
    for(CStringW token = str.Tokenize(L":.,", i); !token.IsEmpty(); token = str.Tokenize(L":.,", i))
        sl.AddHead(token);

    if(sl.GetCount() > 4)
        return(-1);

    int time = 0;

    int mul[4] = {1, 1000, 60 * 1000, 60 * 60 * 1000};
    POSITION pos = sl.GetHeadPosition();
    for(i = 0; pos; i++)
    {
        const WCHAR* s = sl.GetNext(pos);
        WCHAR* tmp = NULL;
        int t = wcstol(s, &tmp, 10);
        if(s >= tmp) return(-1);
        time += t * mul[i];
    }

    return(time);
}

static DWORD StringToDWORD(CStringW str)
{
    if(str.IsEmpty()) return(0);
    if(str[0] == '#') return((DWORD)wcstol(str, NULL, 16));
    else return((DWORD)wcstol(str, NULL, 10));
}

static DWORD ColorToDWORD(CStringW str)
{
    if(str.IsEmpty()) return(0);

    DWORD ret = 0;

    if(str[0] == '#')
    {
        ret = (DWORD)wcstol(str.TrimLeft('#'), NULL, 16);
    }
    else
    {
        g_colors.Lookup(CString(str), ret);
    }

    ret = ((ret & 0xff) << 16) | (ret & 0xff00ff00) | ((ret >> 16) & 0xff);

    return(ret);
}

static int TranslateAlignment(CStringW alignment)
{
    return
        !alignment.CompareNoCase(L"BottomLeft") ? 1 :
        !alignment.CompareNoCase(L"BottomCenter") ? 2 :
        !alignment.CompareNoCase(L"BottomRight") ? 3 :
        !alignment.CompareNoCase(L"MiddleLeft") ? 4 :
        !alignment.CompareNoCase(L"MiddleCenter") ? 5 :
        !alignment.CompareNoCase(L"MiddleRight") ? 6 :
        !alignment.CompareNoCase(L"TopLeft") ? 7 :
        !alignment.CompareNoCase(L"TopCenter") ? 8 :
        !alignment.CompareNoCase(L"TopRight") ? 9 :
        2;
}

static int TranslateMargin(CStringW margin, int wndsize)
{
    int ret = 0;

    if(!margin.IsEmpty())
    {
        ret = wcstol(margin, NULL, 10);
        if(margin.Find('%') >= 0) ret = wndsize * ret / 100;
    }

    return(ret);
}

////////////////

CUSFSubtitles::CUSFSubtitles()
{
}

CUSFSubtitles::~CUSFSubtitles()
{
}

bool CUSFSubtitles::Read(LPCTSTR fn)
{
    VARIANT_BOOL vb;
    CComPtr<IXMLDOMDocument> pDoc;
    if(FAILED(pDoc.CoCreateInstance(CLSID_DOMDocument))
       || FAILED(pDoc->put_async(VARIANT_FALSE))
       || FAILED(pDoc->load(CComVariant(fn), &vb)) || vb != VARIANT_TRUE)
        return(false);

    styles.RemoveAll();
    effects.RemoveAll();
    texts.RemoveAll();

    if(!ParseUSFSubtitles(CComQIPtr<IXMLDOMNode>(pDoc)))
        return(false);

    POSITION pos = styles.GetHeadPosition();
    while(pos)
    {
        style_t* def = styles.GetNext(pos);

        if(def->name.CompareNoCase(L"Default"))
            continue;

        POSITION pos2 = styles.GetHeadPosition();
        while(pos2)
        {
            style_t* s = styles.GetNext(pos2);

            if(!s->name.CompareNoCase(L"Default"))
                continue;

            if(s->fontstyle.face.IsEmpty()) s->fontstyle.face = def->fontstyle.face;
            if(s->fontstyle.size.IsEmpty()) s->fontstyle.size = def->fontstyle.size;
            if(s->fontstyle.color[0].IsEmpty()) s->fontstyle.color[0] = def->fontstyle.color[0];
            if(s->fontstyle.color[1].IsEmpty()) s->fontstyle.color[1] = def->fontstyle.color[1];
            if(s->fontstyle.color[2].IsEmpty()) s->fontstyle.color[2] = def->fontstyle.color[2];
            if(s->fontstyle.color[3].IsEmpty()) s->fontstyle.color[3] = def->fontstyle.color[3];
            if(s->fontstyle.italic.IsEmpty()) s->fontstyle.italic = def->fontstyle.italic;
            if(s->fontstyle.weight.IsEmpty()) s->fontstyle.weight = def->fontstyle.weight;
            if(s->fontstyle.underline.IsEmpty()) s->fontstyle.underline = def->fontstyle.underline;
            if(s->fontstyle.alpha.IsEmpty()) s->fontstyle.alpha = def->fontstyle.alpha;
            if(s->fontstyle.outline.IsEmpty()) s->fontstyle.outline = def->fontstyle.outline;
            if(s->fontstyle.shadow.IsEmpty()) s->fontstyle.shadow = def->fontstyle.shadow;
            if(s->fontstyle.wrap.IsEmpty()) s->fontstyle.wrap = def->fontstyle.wrap;
            if(s->pal.alignment.IsEmpty()) s->pal.alignment = def->pal.alignment;
            if(s->pal.relativeto.IsEmpty()) s->pal.relativeto = def->pal.relativeto;
            if(s->pal.horizontal_margin.IsEmpty()) s->pal.horizontal_margin = def->pal.horizontal_margin;
            if(s->pal.vertical_margin.IsEmpty()) s->pal.vertical_margin = def->pal.vertical_margin;
            if(s->pal.rotate[0].IsEmpty()) s->pal.rotate[0] = def->pal.rotate[0];
            if(s->pal.rotate[1].IsEmpty()) s->pal.rotate[1] = def->pal.rotate[1];
            if(s->pal.rotate[2].IsEmpty()) s->pal.rotate[2] = def->pal.rotate[2];
        }

        break;
    }

    pos = texts.GetHeadPosition();
    while(pos)
    {
        text_t* t = texts.GetNext(pos);
        if(t->style.IsEmpty()) t->style = L"Default";
    }

    return(true);
}

bool CUSFSubtitles::ConvertToSTS(CSimpleTextSubtitle& sts)
{
    sts.m_name = metadata.language.text;
    sts.m_encoding = CTextFile::UTF8;
    sts.m_dstScreenSize = CSize(640, 480);
    sts.m_fScaledBAS = true;
    sts.m_defaultWrapStyle = 1;

    // TODO: map metadata.language.code to charset num (windows doesn't have such a function...)
    int charSet = DEFAULT_CHARSET;

    POSITION pos = styles.GetHeadPosition();
    while(pos)
    {
        style_t* s = styles.GetNext(pos);

        if(!s->name.CompareNoCase(L"Default") && !s->fontstyle.wrap.IsEmpty())
        {
            sts.m_defaultWrapStyle =
                !s->fontstyle.wrap.CompareNoCase(L"no") ? 2 :
                !s->fontstyle.wrap.CompareNoCase(L"auto") ? 1 :
                1;
        }

        STSStyle* stss = DNew STSStyle;
        if(!stss) continue;

        if(!s->pal.horizontal_margin.IsEmpty())
            stss->marginRect.left = stss->marginRect.right = TranslateMargin(s->pal.horizontal_margin, sts.m_dstScreenSize.cx);
        if(!s->pal.vertical_margin.IsEmpty())
            stss->marginRect.top = stss->marginRect.bottom = TranslateMargin(s->pal.vertical_margin, sts.m_dstScreenSize.cy);

        stss->scrAlignment = TranslateAlignment(s->pal.alignment);

        if(!s->pal.relativeto.IsEmpty()) stss->relativeTo =
                !s->pal.relativeto.CompareNoCase(L"window") ? 0 :
                !s->pal.relativeto.CompareNoCase(L"video") ? 1 :
                0;

        stss->borderStyle = 0;
        if(!s->fontstyle.outline.IsEmpty()) stss->outlineWidthX = stss->outlineWidthY = wcstol(s->fontstyle.outline, NULL, 10);
        if(!s->fontstyle.shadow.IsEmpty()) stss->shadowDepthX = stss->shadowDepthY = wcstol(s->fontstyle.shadow, NULL, 10);

        for(ptrdiff_t i = 0; i < 4; i++)
        {
            DWORD color = ColorToDWORD(s->fontstyle.color[i]);
            int alpha = (BYTE)wcstol(s->fontstyle.alpha, NULL, 10);

            stss->colors[i] = color & 0xffffff;
            stss->alpha[i] = (BYTE)(color >> 24);

            stss->alpha[i] = stss->alpha[i] + (255 - stss->alpha[i]) * min(max(alpha, 0), 100) / 100;
        }

        if(!s->fontstyle.face.IsEmpty()) stss->fontName = s->fontstyle.face;
        if(!s->fontstyle.size.IsEmpty()) stss->fontSize = wcstol(s->fontstyle.size, NULL, 10);
        if(!s->fontstyle.weight.IsEmpty()) stss->fontWeight =
                !s->fontstyle.weight.CompareNoCase(L"normal") ? FW_NORMAL :
                !s->fontstyle.weight.CompareNoCase(L"bold") ? FW_BOLD :
                !s->fontstyle.weight.CompareNoCase(L"lighter") ? FW_LIGHT :
                !s->fontstyle.weight.CompareNoCase(L"bolder") ? FW_SEMIBOLD :
                wcstol(s->fontstyle.weight, NULL, 10);
        if(stss->fontWeight == 0) stss->fontWeight = FW_NORMAL;
        if(!s->fontstyle.italic.IsEmpty()) stss->fItalic = s->fontstyle.italic.CompareNoCase(L"yes") == 0;
        if(!s->fontstyle.underline.IsEmpty()) stss->fUnderline = s->fontstyle.underline.CompareNoCase(L"yes") == 0;
        if(!s->pal.rotate[0].IsEmpty()) stss->fontAngleZ = wcstol(s->pal.rotate[0], NULL, 10);
        if(!s->pal.rotate[1].IsEmpty()) stss->fontAngleX = wcstol(s->pal.rotate[1], NULL, 10);
        if(!s->pal.rotate[2].IsEmpty()) stss->fontAngleY = wcstol(s->pal.rotate[2], NULL, 10);

        stss->charSet = charSet;

        sts.AddStyle(WToT(s->name), stss);
    }

    pos = texts.GetHeadPosition();
    while(pos)
    {
        text_t* t = texts.GetNext(pos);

        if(!t->pal.alignment.IsEmpty())
        {
            CStringW s;
            s.Format(L"{\\an%d}", TranslateAlignment(t->pal.alignment));
            t->str = s + t->str;
        }

        CRect marginRect;
        marginRect.SetRectEmpty();

        if(!t->pal.horizontal_margin.IsEmpty())
            marginRect.left = marginRect.right = TranslateMargin(t->pal.horizontal_margin, sts.m_dstScreenSize.cx);
        if(!t->pal.vertical_margin.IsEmpty())
            marginRect.top = marginRect.bottom = TranslateMargin(t->pal.vertical_margin, sts.m_dstScreenSize.cy);

        WCHAR rtags[3][8] = {L"{\\rz%d}", L"{\\rx%d}", L"{\\ry%d}"};
        for(ptrdiff_t i = 0; i < 3; i++)
        {
            if(int angle = wcstol(t->pal.rotate[i], NULL, 10))
            {
                CStringW str;
                str.Format(rtags[i], angle);
                t->str = str + t->str;
            }
        }

        if(t->style.CompareNoCase(L"Default") != 0)
        {
            POSITION pos = styles.GetHeadPosition();
            while(pos)
            {
                style_t* s = styles.GetNext(pos);
                if(s->name == t->style && !s->fontstyle.wrap.IsEmpty())
                {
                    int WrapStyle =
                        !s->fontstyle.wrap.CompareNoCase(L"no") ? 2 :
                        !s->fontstyle.wrap.CompareNoCase(L"auto") ? 1 :
                        1;

                    if(WrapStyle != sts.m_defaultWrapStyle)
                    {
                        CStringW str;
                        str.Format(L"{\\q%d}", WrapStyle);
                        t->str = str + t->str;
                    }

                    break;
                }
            }
        }

        // TODO: apply effects as {\t(..)} after usf's standard clearly defines them

        sts.Add(t->str, true, t->start, t->stop, WToT(t->style), _T(""), _T(""), marginRect);
    }

    return(true);
}

bool CUSFSubtitles::ParseUSFSubtitles(CComPtr<IXMLDOMNode> pNode)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"usfsubtitles")
    {
        // metadata

        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"metadata")
            {
                ParseMetadata(pChild, metadata);
            }
        }
        EndEnumChildren

        // styles

        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"styles")
            {
                BeginEnumChildren(pChild, pGrandChild) // :)
                {
                    DeclareNameAndValue(pGrandChild, name, val);

                    if(name == L"style")
                    {
                        CAutoPtr<style_t> s(DNew style_t);
                        if(s)
                        {
                            ParseStyle(pGrandChild, s);
                            styles.AddTail(s);
                        }
                    }
                }
                EndEnumChildren
            }
        }
        EndEnumChildren

        // effects

        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"effects")
            {
                BeginEnumChildren(pChild, pGrandChild) // :)
                {
                    DeclareNameAndValue(pGrandChild, name, val);

                    if(name == L"effect")
                    {
                        CAutoPtr<effect_t> e(DNew effect_t);
                        if(e)
                        {
                            ParseEffect(pGrandChild, e);
                            effects.AddTail(e);
                        }
                    }
                }
                EndEnumChildren
            }
        }
        EndEnumChildren

        // subtitles

        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"subtitles")
            {
                BeginEnumChildren(pChild, pGrandChild) // :)
                {
                    DeclareNameAndValue(pGrandChild, name, val);

                    if(name == L"subtitle")
                    {
                        CStringW sstart = GetAttrib(L"start", pGrandChild);
                        CStringW sstop = GetAttrib(L"stop", pGrandChild);
                        CStringW sduration = GetAttrib(L"duration", pGrandChild);
                        if(sstart.IsEmpty() || (sstop.IsEmpty() && sduration.IsEmpty()))
                            continue;

                        int start = TimeToInt(sstart);
                        int stop = !sstop.IsEmpty() ? TimeToInt(sstop) : (start + TimeToInt(sduration));

                        ParseSubtitle(pGrandChild, start, stop);
                    }
                }
                EndEnumChildren
            }
        }
        EndEnumChildren

        return(true);
    }

    BeginEnumChildren(pNode, pChild)
    {
        if(ParseUSFSubtitles(pChild))
        {
            return(true);
        }
    }
    EndEnumChildren

    return(false);
}

void CUSFSubtitles::ParseMetadata(CComPtr<IXMLDOMNode> pNode, metadata_t& m)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"title")
    {
        m.title = GetText(pNode);
    }
    else if(name == L"date")
    {
        m.date = GetText(pNode);
    }
    else if(name == L"comment")
    {
        m.comment = GetText(pNode);
    }
    else if(name == L"author")
    {
        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"name")
                m.author.name = GetText(pChild);
            else if(name == L"email")
                m.author.email = GetText(pChild);
            else if(name == L"url")
                m.author.url = GetText(pChild);
        }
        EndEnumChildren

        return;
    }
    else if(name == L"language")
    {
        m.language.text = GetText(pNode);
        m.language.code = GetAttrib(L"code", pNode);
    }
    else if(name == L"languageext")
    {
        m.languageext.text = GetText(pNode);
        m.languageext.code = GetAttrib(L"code", pNode);
    }

    BeginEnumChildren(pNode, pChild)
    {
        ParseMetadata(pChild, metadata);
    }
    EndEnumChildren
}

void CUSFSubtitles::ParseStyle(CComPtr<IXMLDOMNode> pNode, style_t* s)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"style")
    {
        s->name = GetAttrib(L"name", pNode);
    }
    else if(name == L"fontstyle")
    {
        ParseFontstyle(pNode, s->fontstyle);
        return;
    }
    else if(name == L"position")
    {
        ParsePal(pNode, s->pal);
        return;
    }

    BeginEnumChildren(pNode, pChild)
    {
        ParseStyle(pChild, s);
    }
    EndEnumChildren
}

void CUSFSubtitles::ParseFontstyle(CComPtr<IXMLDOMNode> pNode, fontstyle_t& fs)
{
    fs.face = GetAttrib(L"face", pNode);
    fs.size = GetAttrib(L"size", pNode);
    fs.color[0] = GetAttrib(L"color", pNode);
    fs.color[1] = GetAttrib(L"back-color", pNode);
    fs.color[2] = GetAttrib(L"outline-color", pNode);
    fs.color[3] = GetAttrib(L"shadow-color", pNode);
    fs.italic = GetAttrib(L"italic", pNode);
    fs.weight = GetAttrib(L"weight", pNode);
    fs.underline = GetAttrib(L"underline", pNode);
    fs.alpha = GetAttrib(L"alpha", pNode);
    fs.outline = GetAttrib(L"outline-level", pNode);
    fs.shadow = GetAttrib(L"shadow-level", pNode);
    fs.wrap = GetAttrib(L"wrap", pNode);
}

void CUSFSubtitles::ParsePal(CComPtr<IXMLDOMNode> pNode, posattriblist_t& pal)
{
    pal.alignment = GetAttrib(L"alignment", pNode);
    pal.relativeto = GetAttrib(L"relative-to", pNode);
    pal.horizontal_margin = GetAttrib(L"horizontal-margin", pNode);
    pal.vertical_margin = GetAttrib(L"vertical-margin", pNode);
    pal.rotate[0] = GetAttrib(L"rotate-z", pNode);
    pal.rotate[1] = GetAttrib(L"rotate-x", pNode);
    pal.rotate[2] = GetAttrib(L"rotate-y", pNode);
}

void CUSFSubtitles::ParseEffect(CComPtr<IXMLDOMNode> pNode, effect_t* e)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"effect")
    {
        e->name = GetAttrib(L"name", pNode);
    }
    else if(name == L"keyframes")
    {
        BeginEnumChildren(pNode, pChild)
        {
            DeclareNameAndValue(pChild, name, val);

            if(name == L"keyframe")
            {
                CAutoPtr<keyframe_t> k(DNew keyframe_t);
                if(k)
                {
                    ParseKeyframe(pChild, k);
                    e->keyframes.AddTail(k);
                }
            }
        }
        EndEnumChildren

        return;
    }

    BeginEnumChildren(pNode, pChild)
    {
        ParseEffect(pChild, e);
    }
    EndEnumChildren
}

void CUSFSubtitles::ParseKeyframe(CComPtr<IXMLDOMNode> pNode, keyframe_t* k)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"keyframe")
    {
        k->position = GetAttrib(L"position", pNode);
    }
    else if(name == L"fontstyle")
    {
        ParseFontstyle(pNode, k->fontstyle);
        return;
    }
    else if(name == L"position")
    {
        ParsePal(pNode, k->pal);
        return;
    }
}

void CUSFSubtitles::ParseSubtitle(CComPtr<IXMLDOMNode> pNode, int start, int stop)
{
    DeclareNameAndValue(pNode, name, val);

    if(name == L"text" || name == L"karaoke")
    {
        CAutoPtr<text_t> t(DNew text_t);
        if(t)
        {
            t->start = start;
            t->stop = stop;
            t->style = GetAttrib(L"style", pNode);
            t->effect = GetAttrib(L"effect", pNode);
            ParsePal(pNode, t->pal);
            ParseText(pNode, t->str);
            texts.AddTail(t);
        }

        return;
    }
//	else if

    BeginEnumChildren(pNode, pChild)
    {
        ParseSubtitle(pChild, start, stop);
    }
    EndEnumChildren
}

void CUSFSubtitles::ParseText(CComPtr<IXMLDOMNode> pNode, CStringW& str)
{
    DeclareNameAndValue(pNode, name, val);

    CStringW prefix, postfix;

    if(name == L"b")
    {
        prefix = L"{\\b1}";
        postfix = L"{\\b}";
    }
    else if(name == L"i")
    {
        prefix = L"{\\i1}";
        postfix = L"{\\i}";
    }
    else if(name == L"u")
    {
        prefix = L"{\\u1}";
        postfix = L"{\\u}";
    }
    else if(name == L"font")
    {
        fontstyle_t fs;
        ParseFontstyle(pNode, fs);

        if(!fs.face.IsEmpty())
        {
            prefix += L"{\\fn" + fs.face + L"}";
            postfix += L"{\\fn}";
        }
        if(!fs.size.IsEmpty())
        {
            prefix += L"{\\fs" + fs.size + L"}";
            postfix += L"{\\fs}";
        }
        if(!fs.outline.IsEmpty())
        {
            prefix += L"{\\bord" + fs.outline + L"}";
            postfix += L"{\\bord}";
        }
        if(!fs.shadow.IsEmpty())
        {
            prefix += L"{\\shad" + fs.shadow + L"}";
            postfix += L"{\\shad}";
        }

        for(ptrdiff_t i = 0; i < 4; i++)
        {
            if(!fs.color[i].IsEmpty())
            {
                CStringW s;
                s.Format(L"{\\%dc&H%06x&}", i + 1, ColorToDWORD(fs.color[i]));
                prefix += s;
                s.Format(L"{\\%dc}", i + 1);
                postfix += s;
            }
        }
    }
    else if(name == L"k")
    {
        int t = wcstol(GetAttrib(L"t", pNode), NULL, 10);
        CStringW s;
        s.Format(L"{\\kf%d}", t / 10);
        str += s;
        return;
    }
    else if(name == L"br")
    {
        str += L"\\N";
        return;
    }
    else if(name == L"#text")
    {
        str += GetXML(pNode);
        return;
    }

    BeginEnumChildren(pNode, pChild)
    {
        CStringW s;
        ParseText(pChild, s);
        str += s;
    }
    EndEnumChildren

    str = prefix + str + postfix;
}

void CUSFSubtitles::ParseShape(CComPtr<IXMLDOMNode> pNode)
{
    // no specs on this yet
}
